const fs = require('fs'),
  log = require('./log'),
  Buffer = require("buffer").Buffer,
  crypto = require("crypto"),
  Client = require('ssh2').Client,
  separator = require('os').platform() == 'linux' ? '/' : '\\',
  conn = new Client();

var sftp,
  localPath = '',
  remotePath = '',
  username = '',
  password = '',
  host = '',
  arguments = process.argv.splice(2);

for (var i = 0; i < arguments.length;) {
  switch (arguments[i]) {
    case 'local':
      localPath = arguments[i + 1]
      i += 2
      break
    case 'remote':
      remotePath = arguments[i + 1]
      i += 2
      break
    case 'username':
      username = arguments[i + 1]
      i += 2
      break
    case 'password':
      password = arguments[i + 1]
      i += 2
      break
    case 'host':
      host = arguments[i + 1]
      i += 2
      break
    default:
      i++
  }
}
if (localPath == '' && remotePath == '') {
  localPath = __dirname + separator + '..' + separator + '..'
  remotePath = '/root/soft/src/zoro'
}
if (localPath == '') {
  remotePath = __dirname + separator + '..' + separator + '..' + separator + '..' + separator + remotePath.substring(remotePath.lastIndexOf('/') + 1);
}
if (remotePath == '') {
  remotePath = '/root/soft/src/' + localPath.substring(localPath.lastIndexOf(separator) + 1);
}
if (username == '') {
  username = 'root'
}
if (password == '') {
  password = '<SB>buyaodenglu'
}
if (host == '') {
  host = '123.56.115.107'//'139.129.45.92'
}
new Promise(resolve => {
  fs.exists(localPath + separator + 'package.json', exists => {
    if (!exists) {
      log(localPath + '下不存在package.json')
      return
    }
    log('sync ' + localPath + ' to ' + remotePath)
    resolve()
  })
}).then(() => {
  var p1 = new Promise(resolve => {
      new Promise(_resolve => {
        fs.exists(localPath + separator + '.db', (exists) => {
          if (exists) {
            _resolve(localPath + separator + '.db')
          } else {
            fs.exists(localPath + separator + '.odb', (exists) => {
              if (exists) {
                _resolve(localPath + separator + '.odb')
              } else {
                resolve([])
              }
            })
          }
        })
      }).then(filename => {
        getFileMetaListFromFile(filename, true, resolve)
      })
    }),
    p2 = new Promise(resolve => {
      var promiseList = []
      fs.readFile(localPath + separator + '.exclude', 'utf8', (err, data) => {
        if (err) {
          throw err
        }
        var excludePaths = data.split('\n');
        if (excludePaths.indexOf('.db') == -1) {
          excludePaths.push('.db')
        }
        if (excludePaths.indexOf('.rdb') == -1) {
          excludePaths.push('.rdb')
        }
        if (excludePaths.indexOf('.odb') == -1) {
          excludePaths.push('.odb')
        }
        if (excludePaths.indexOf('node_modules') == -1) {
          excludePaths.push('node_modules')
        }
        fs.readdir(localPath, (err, files) => {
          files.forEach(subPath => {
            if (excludePaths.indexOf(subPath) != -1) {
              return
            }
            promiseList.push(new Promise(resolve => {
              fs.exists(localPath + separator + subPath, exists => {
                if (exists)
                  getFileMd5(localPath + separator + subPath, resolve);
                else
                  resolve([])
              })
            }))
          })
          Promise.all(promiseList).then(fileMetaLists => {
            var fileMetaList = []
            fileMetaLists.forEach(_fileMetaList => {
              _fileMetaList.forEach(fileMeta => {
                fileMetaList.push(fileMeta)
              })
            })
            resolve(fileMetaList)
          }).catch(err => {
            log(err)
          })
        })
      })
    }),
    p3 = new Promise(resolve => {
      conn.on('ready', () => {
        conn.sftp((err, _sftp) => {
          if (err)
            throw err;
          sftp = _sftp;
          sftp.fastGet(remotePath + '/.db', localPath + separator + '.rdb', {}, err => {
            if(err) {
              resolve([])
              return
            }
            getFileMetaListFromFile(localPath + separator + '.rdb', false, resolve)
          })
        })
      }).connect({
        host: host,
        port: 22,
        username: username,
        password: password
      })
    });
  Promise.all([p1, p2, p3]).then(result => {
    result[1].forEach(fileMeta => {
      var isNewFile = true;
      result[0].forEach(_fileMeta => {
        if (!isNewFile)
          return;
        if (fileMeta.name == _fileMeta.name) {
          _fileMeta.newMd5 = fileMeta.newMd5;
          isNewFile = false
        }
      })
      if (isNewFile) {
        result[0].push({
          name: fileMeta.name,
          oldMd5: '0',
          oldVersion: 0,
          newMd5: fileMeta.newMd5,
          remoteMd5: '0',
          remoteVersion: 0
        })
      }
    })
    result[2].forEach(fileMeta => {
      var isNewFile = true;
      result[0].forEach(_fileMeta => {
        if (!isNewFile)
          return;
        if (fileMeta.name == _fileMeta.name) {
          _fileMeta.remoteMd5 = fileMeta.remoteMd5;
          _fileMeta.remoteVersion = fileMeta.remoteVersion;
          isNewFile = false
        }
      })
      if (isNewFile) {
        result[0].push({
          name: fileMeta.name,
          oldMd5: '0',
          oldVersion: 0,
          newMd5: '0',
          remoteMd5: fileMeta.remoteMd5,
          remoteVersion: fileMeta.remoteVersion
        })
      }
    })
    for (var i = 0; i < result[0].length; i++) {
      var fileMeta = result[0][i];
      if (fileMeta.oldMd5 != fileMeta.newMd5 && fileMeta.oldMd5 != fileMeta.remoteMd5 && fileMeta.newMd5 != fileMeta.remoteMd5) {
        log(fileMeta.name + ":冲突:" + fileMeta.oldMd5 + '(' + fileMeta.oldVersion + '):' + fileMeta.newMd5 + ':' + fileMeta.remoteMd5 + '(' + fileMeta.remoteVersion + ')')
        conn.end()
        return
      }
    }
    var data = '',
      filesForUpload = [],
      filesForDownload = [],
      filesForDeleteRemote = [],
      filesForDeleteLocal = []
    result[0].forEach(fileMeta => {
      var md5 = fileMeta.oldMd5,
        version = fileMeta.oldVersion;
      if (md5 == fileMeta.newMd5) { // 以前，新加一致
        if (md5 == fileMeta.remoteMd5) { // 以前，新加，远程一致
          if (version < fileMeta.remoteVersion)
            version = fileMeta.remoteVersion
        } else if (version > fileMeta.remoteVersion) { // 以前，新加一致，远程不一致，以前版本较大
          if (md5 == "0") { // 以前，新加一致，远程不一致，以前版本较大，以前已被删，删除远程
            log(fileMeta.name + ":本地删除:" + version)
            filesForDeleteRemote.push(fileMeta.name)
          } else { // 以前，新加一致，远程不一致，以前版本较大，以前真的有，上传
            log(fileMeta.name + ":本地更新:" + version + ":" + fileMeta.remoteVersion)
            filesForUpload.push(fileMeta.name)
          }
        } else { // 以前，新加一致，远程不一致，以前版本较小
          md5 = fileMeta.remoteMd5;
          version = fileMeta.remoteVersion;
          if (md5 == "0") { // 以前，新加一致，远程不一致，以前版本较小，远程已被删，删除本地
            log(fileMeta.name + ":远程删除:" + version)
            filesForDeleteLocal.push(fileMeta.name)
          } else { // 以前，新加一致，远程不一致，以前版本较小，远程真的有，下载
            log(fileMeta.name + ":远程更新:" + version)
            filesForDownload.push(fileMeta.name)
          }
        }
      } else if (md5 == fileMeta.remoteMd5) { // 以前，远程一致，新加不一致
        md5 = fileMeta.newMd5;
        if (version < fileMeta.remoteVersion)
          version = fileMeta.remoteVersion;
        version++;
        if (md5 == "0") { // 以前，远程一致，新加不一致，新加已被删，删除远程
          log(fileMeta.name + ":本地删除:" + version)
          filesForDeleteRemote.push(fileMeta.name)
        } else { // 以前，远程一致，新加不一致，新加真的有，上传
          log(fileMeta.name + ":本地更新:" + version)
          filesForUpload.push(fileMeta.name)
        }
      } else { // 新加，远程一致，以前不一致
        md5 = fileMeta.newMd5;
        if (version > fileMeta.remoteVersion) { // 新加，远程一致，以前不一致，以前版本较大
          version++;
        } else { // 新加，远程一致，以前不一致，以前版本较小
          version = fileMeta.remoteVersion;
        }
      }
      data += fileMeta.name + ' ' + md5 + ' ' + version + '\n'
    })

    log('开始操作文件')

    var p0 = new Promise(resolve => {
        uploadFiles(filesForUpload, resolve)
      }),
      p1 = new Promise(resolve => {
        downloadFiles(filesForDownload, resolve)
      }),
      p2 = new Promise(resolve => {
        deleteRemoteFiles(filesForDeleteRemote, resolve)
      }),
      p3 = new Promise(resolve => {
        deleteLocalFiles(filesForDeleteLocal, resolve)
      });
    Promise.all([p0, p1, p2, p3]).then(result => {
      var p1 = new Promise(resolve => {
          fs.writeFile(localPath + separator + '.db', data, err => {
            if (err) {
              throw err
              return
            }
            resolve()
          })
        }),
        p2 = new Promise(resolve => {
          sftp.writeFile(remotePath + '/.db', data, {}, () => {
            resolve()
          })
        });
      Promise.all([p1, p2]).then(result => {
        conn.end()
      }).catch(err => {
        log(err)
      })
    }).catch(err => {
      log(err)
    })
  }).catch(err => {
    log(err)
    conn.end()
  })
}).catch(err => {
  log(err)
});

function getFileMd5(filename, resolve) {
  fs.lstat(filename, (err, stats) => {
    if (err)
      throw err;
    if (stats.isFile()) {
      fs.readFile(filename, (err, data) => {
        resolve([{
          name: filename.substring(localPath.length + 1),
          newMd5: crypto.createHash("md5").update(data).digest("hex")
        }
        ])
      })
    } else {
      fs.readdir(filename, (err, files) => {
        var promiseList = []
        files.forEach(file => {
          promiseList.push(new Promise(resolve => {
            getFileMd5(filename + separator + file, resolve)
          }))
        })
        Promise.all(promiseList).then(fileMetaLists => {
          var fileMetaList = []
          fileMetaLists.forEach(_fileMetaList => {
            _fileMetaList.forEach(fileMeta => {
              fileMetaList.push(fileMeta)
            })
          })
          resolve(fileMetaList)
        })
      })
    }
  })
}

function getFileMetaListFromFile(filename, isLocalDB, resolve) {
  fs.exists(filename, exists => {
    if (!exists) {
      resolve([])
      return
    }
    fs.readFile(filename, 'utf8', (err, data) => {
      if (err) {
        log(err)
        return
      }
      var fileMetaStrList = data.split('\n'),
        fileMetaList = [];
      fileMetaStrList.forEach(fileMetaStr => {
        if (fileMetaStr == '')
          return;
        var fileMetaArray = fileMetaStr.split(' ');
        if (fileMetaArray.length != 3) {
          log('遇见奇怪的行:' + fileMetaStr)
          return
        }
        if (isLocalDB) {
          fileMetaList.push({
            name: fileMetaArray[0],
            oldMd5: fileMetaArray[1],
            oldVersion: parseInt(fileMetaArray[2]),
            newMd5: '0',
            remoteMd5: '0',
            remoteVersion: 0
          })
        } else {
          fileMetaList.push({
            name: fileMetaArray[0],
            remoteMd5: fileMetaArray[1],
            remoteVersion: parseInt(fileMetaArray[2])
          })
        }
      })
      resolve(fileMetaList)
    })
  })
}

function mkrdir(basePath, subPath, resolve) {
  sftp.exists(basePath, exists => {
    if (!exists) {
      var start = basePath.lastIndexOf('/');
      subPath = basePath.substring(start + 1) + '/' + subPath;
      basePath = basePath.substring(0, start);
      mkrdir(basePath, subPath, resolve)
      return
    }
    var start = subPath.indexOf('/'),
      targetPath = basePath + '/' + (start == -1 ? subPath : subPath.substring(0, start));
    new Promise(_resolve => {
      sftp.exists(targetPath, exists => {
        if (!exists) {
          log('mkdir ' + targetPath)
          sftp.mkdir(targetPath, err => {
            if (err)
              throw err;
            _resolve()
          })
          return
        }
        _resolve()
      })
    }).then(result => {
      if (start != -1) {
        mkrdir(targetPath, subPath.substring(start + 1), resolve)
        return
      }
      resolve();
    }).catch(err => {
      log(err)
    })
  })
}

function mkldir(basePath, subPath, resolve) {
  fs.exists(basePath, exists => {
    if (!exists) {
      var start = basePath.lastIndexOf(separator);
      subPath = basePath.substring(start + 1) + separator + subPath;
      basePath = basePath.substring(0, start);
      mkldir(basePath, subPath, resolve)
      return
    }
    var start = subPath.indexOf(separator),
      targetPath = basePath + separator + (start == -1 ? subPath : subPath.substring(0, start));
    new Promise(_resolve => {
      fs.exists(targetPath, exists => {
        if (!exists) {
          fs.mkdir(targetPath, err => {
            if (err)
              throw err;
            _resolve()
          })
          return
        }
        _resolve();
      })
    }).then(result => {
      if (start != -1) {
        mkldir(targetPath, subPath.substring(start + 1), resolve)
        return
      }
      resolve();
    }).catch(err => {
      log(err)
    })
  })
}

function uploadFiles(nameList, resolve) {
  if (nameList.length == 0) {
    resolve()
    return
  }
  const name = nameList[0],
    localFile = localPath + separator + name,
    remoteFile = remotePath + '/' + name.replace(/\\/g, '/');
  mkrdir(remotePath, name.substring(0, name.lastIndexOf(separator)).replace(/\\/g, '/'), () => {
    sftp.fastPut(localFile, remoteFile, {}, err => {
      if (err) {
        log(name)
        throw err
      }
      uploadFiles(nameList.slice(1), resolve)
    })
  })
}

function downloadFiles(nameList, resolve) {
  if (nameList.length == 0) {
    resolve()
    return
  }
  const name = nameList[0],
    localFile = localPath + separator + name,
    remoteFile = remotePath + '/' + name.replace(/\\/g, '/');
  mkldir(localPath, name.substring(0, name.lastIndexOf(separator)), () => {
    sftp.fastGet(remoteFile, localFile, {}, err => {
      if (err) {
        log(remoteFile)
        throw err
      }
      downloadFiles(nameList.slice(1), resolve)
    })
  })
}

function deleteRemoteFiles(nameList, resolve) {
  if (nameList.length == 0) {
    resolve()
    return
  }
  const remoteFile = remotePath + '/' + nameList[0].replace(/\\/g, '/');
  sftp.exists(remoteFile, exists => {
    if (exists) {
      sftp.unlink(remoteFile, () => {
        deleteRemoteFiles(nameList.slice(1), resolve)
      })
    } else {
      deleteRemoteFiles(nameList.slice(1), resolve)
    }
  })
}

function deleteLocalFiles(nameList, resolve) {
  if (nameList.length == 0) {
    resolve()
    return
  }
  fs.unlink(localPath + separator + nameList[0], () => {
    deleteLocalFiles(nameList.slice(1), resolve)
  })
}
